refactor(data-objectstack,plugin-chatbot,plugin-list)!: 台账燃尽批次 6 —— 12 个符号不再顶着 spec 的名字 (#3160) - #3220
Merged
Merged
Conversation
…atch 6 — 12 symbols stop wearing spec names
objectui#3160, objectstack#4115 ledger batch 6. All three packages leave the
ledger (29 -> 17 collisions, 14 -> 11 packages).
Verdicts, per SYMBOL rather than per cluster — the batch's own triage note
predicted `SecurityPolicy` and `DroppedFieldsEvent` were both spec hand copies,
and only one of them was:
@object-ui/data-objectstack
- DroppedFieldsEvent -> re-export. The copy's comment said it mirrored the
spec "so we don't pin a client type version" and widened `reason` to bare
`string` "for forward-compatibility". The spec IS the client type version,
and the widening deleted the only signal that would tell AdapterProvider's
toast (it branches on `readonly_when`) that a third reason had appeared.
- CacheStats -> MetadataCacheStats (spec's is ICacheService's)
- MetadataSaveOptions -> MetadataClientSaveOptions (spec's writes a FILE)
- SecurityPolicy -> SecurityManagerPolicy (spec's is supply-chain)
- ValidationError -> DataApiValidationError, per the cross-package
convention registered on objectstack#4115. The RUNTIME `name` stays
'ValidationError': normaliseClientError and @object-ui/react's
error-message both sniff err.name, so that string is a wire contract.
@object-ui/plugin-chatbot
- PendingActionRow / PendingActionStatus -> re-export. Three drifts, each of
which had DISABLED a compile-time check: `status: PendingActionStatus |
string` (a union with string absorbs the literals), `[k: string]: unknown`
(objectstack#4075 — makes every structural comparison answer "identical"),
and created_at/updated_at, which the contract lacks and nothing reads.
- Tool / MessageContent -> SKIP_PATH_SEGMENTS. They live in src/elements/,
vendored Vercel AI Elements + two Shadcn primitives, the same class as the
already-skipped components/src/ui/. The names ARE the upstream component
API, so a rename is undone by the next re-sync. The hole a path skip opens
is closed by a provenance test: every file there must carry its vendor
banner, and the barrel must stay a pure re-export.
@object-ui/plugin-list
- ViewTab -> derived from the spec schema's INPUT side (pinned/isDefault/
visible carry .default()s and this reads authored metadata). Drops a
renderer-side tolerance: `visible` accepted `string | boolean` and the tab
bar compared it against the literal 'false', which no producer emits.
- ListView / UserFilters -> ALLOW. The AuthProvider test, not "components are
exempt": each is the RENDERER of the spec type whose name it shares and
takes that type as a prop (ListViewProps.schema, UserFiltersProps.config),
so neither declares a rival shape. @object-ui/types already aliases the
spec's type as SpecListView from the other side.
Verification: `--ledger` regenerated and set-diffed — removed is exactly these
12, added empty, all 11 other packages byte-identical (batch 7's symbols
untouched). Guard green; turbo build 44/44 and type-check 78/78 --force after
merging origin/main; data-objectstack 153, plugin-chatbot 251, plugin-list 284
and core 153 assertions green. Five mutation directions all red: re-forking
ViewTab (guard by name+file, and the typetests pin), undoing the
ValidationError rename, leaving a burned name in DEBT, deleting the ListView
ALLOW entry, and dropping an authored `Tool` into the skipped vendored dir
(guard blind by design, provenance test red).
Pre-existing and unrelated, filed as objectui#3219: plugin-calendar's
registration.test.tsx fails on main (an @object-ui/react vi.mock missing
`subscribeDataChanges`); reproduced with this branch stashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRJtkgUAaVG11FsJQbvZWA
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3160 — objectstack-ai/objectstack#4115 债务台账燃尽第 6 批。
台账 29 → 17,包数 14 → 11:
@object-ui/data-objectstack、@object-ui/plugin-chatbot、@object-ui/plugin-list三包整包出账。DroppedFieldsEvent、PendingActionRow、PendingActionStatusViewTab(取 schema 的 input 侧)CacheStats→MetadataCacheStats、MetadataSaveOptions→MetadataClientSaveOptions、SecurityPolicy→SecurityManagerPolicy、ValidationError→DataApiValidationErrorListView、UserFiltersTool、MessageContent一、本批最该记的一条:分诊颗粒度是符号,不是簇 —— 又一次实证
issue 正文的已知情报写着「
SecurityPolicy/DroppedFieldsEvent大概率是协议形状的手抄件,优先派生」。对读之后只中了一半:DroppedFieldsEvent确实是手抄件,而且它的注释自己就承认了(见下)。SecurityPolicy与 spec 的那个一个键都不重合。spec 的是软件包供应链策略(autoScan/ 许可证白名单 / 代码签名 / sandbox 限额),本地这个是浏览器侧的 CSP 头 + 审计日志 + 字段脱敏。同理MetadataSaveOptions:spec 的两份(/system、/kernel)都在描述把元数据写成文件(format/path/indent/atomic),本地这个是MetadataClient.save()的 HTTP 信封(If-Match、actor、force、ADR-0033 draft/publish、package)。两对都是Extract< keyof Local, keyof Spec > = never,已在 parity 测试里按这个式子钉住。也就是说:同一句预判里的两条,一条派生、一条纯撞名。这与批次 5 记录的结论一致,只是这次方向相反(那次是「疑似撞名实测可派生」)。
二、三份 hand copy,三种把编译期检查关掉的方式
这批的漂移不是「少了一个键」那么简单,每一处都注销了一个本该生效的检查:
DroppedFieldsEvent.reason被拓宽成string,理由写在注释里:「keepsreasonas a widened string for forward-compatibility with reasons added server-side」。这正是 AGENTS.md [WIP] Enhance UI components for forms and layouts #12 禁止的消费端宽容 —— 而AdapterProvider的 toast 文案真的按readonly_when分支(#3794:「read-only」和「当前状态下不可编辑」是两句不同的话)。spec 加第三个 reason 时,唯一会提醒我们的信号被这行注释亲手删掉了。顺带:「so we don't pin a client type version」也是假的 —— spec 就是那个 client type version。PendingActionRow.status被声明成PendingActionStatus | string—— 与string求并吸收掉全部字面量,这个标注等于什么都没说;statusesForTab完全可以返回一个服务端没听说过的状态。PendingActionRow的[k: string]: unknown—— objectstack#4075 那个机制:索引签名让任何结构比较对 spec 都答「相同」,漂多远都一样。所以这一条只有守卫能抓到,parity 测试原理上抓不到(批次 5 的方法学第 7 条)。另外
PendingActionRow上的created_at/updated_at契约里没有、本仓也没有任何消费方读。一并删掉:真需要时该由 spec 建模,不是在这边加宽。ViewTab同理带一条渲染器侧宽容:visible被声明成string | boolean,而getVisibleTabs拿它跟字符串字面量'false'比。spec 把visible建模为 boolean,平台里没有任何生产方会发出那个字符串。派生之后该比较直接变成编译错误,而不是一段永远不会命中的死代码。三、
ViewTab取 input 侧,判据写在调用形态里spec 的
ViewTab是z.infer,即 parse 之后的形状,pinned/isDefault/visible三个.default()键全部变成必填。而 TabBar 拿到的是宿主从存量视图元数据里塞进来的{ name, label }—— 用 output 类型会让这个授权侧形状不可表达,正是ObjectFieldGroup(#3169)和OfflineConfig(#3199)踩过的_input/_output陷阱。所以走
(typeof ViewTabSchema)['_zod']['input'],并沿用packages/react/src/spec-input.ts的技法从 schema 自身的_zod载体读 input 类型 —— 仍然是 spec 的绑定,不给 plugin-list 新增 zod 依赖。测试里把两侧都钉住了:parsed 侧pinned/visible必填、authored 侧可省,且两者键集完全一致(spec 加键会自动到位,退键会报红)。四、
ListView/UserFilters进 ALLOW —— 用的是AuthProvider那条判据,不是「组件一律豁免」批次 5 已经明确写过:
AuthProvider进 ALLOW 而AuthProviderConfig改名,理由不是组件豁免,而是失效模式本身(同一批里Field→FieldContainer就是被改名的组件)。按同一条判据对读:ListView/UserFilters都是 type-only 的授权层元数据;ListViewProps.schema是@object-ui/types的ListViewSchema(它自己就是批次 1 论证过的 declared dialect),UserFiltersProps.config是NonNullable< ListViewSchema['userFilters'] >;@object-ui/types把 spec 的那个 re-export 成SpecListView;ALLOW 不是白条:两条都配了会过期就报红的钉扎 —— spec 若不再导出这两个名字(ALLOW 就该删)、spec 的
ListView若不再是授权元数据、组件若不再消费那个 spec 类型,测试都会红。五、
Tool/MessageContent走路径跳过,并补上路径跳过的洞这两个在
packages/plugin-chatbot/src/elements/,而那整个目录不是 objectui 的授权面:每个文件头都写着Sourced from vercel/ai-elements (https://elements.ai-sdk.dev) — MIT. Vendored via the shadcn-style copy-into-source model. Do NOT edit business logic directly … To re-sync, fetch the latest from …(elements/ui/下另有两个同样形态的 shadcn 原件)。这与守卫已经在跳过的components/src/ui/是同一类,守卫文件头对那条的措辞逐字适用:「upstream 3rd-party files overwritten by sync scripts, so a collision there is not ours to fix」。改名在这里是错的:这两个名字就是上游的组件 API。改了会被下一次 re-sync 抹掉,并且让照着上游文档把
Tool/ToolHeader/ToolContent组合起来写的人失败。路径跳过比 ALLOW 粗,它开的洞是「objectui 自己写的文件藏进该目录后不被扫描」。所以本 PR 一并把洞堵上:新增的 provenance 测试要求该目录下每个文件都带 vendor banner,且那唯一一个 objectui 写的 barrel 只能是纯 re-export。变异测试第 5 向验证了这一点(见下)。
同时按批次 5
PerformanceConfig的做法配了反向 pin:spec 若哪天退掉Tool/MessageContent,守卫注释里那句「跳过的是这两条碰撞」就过期了,测试报红要求重读 —— 绕行方案不该活得比它的理由长。六、一个刻意没有跟着改的名字
DataApiValidationError的运行时name仍然是'ValidationError'。normaliseClientError用e.name === 'ValidationError'嗅探@objectstack/client抛出的错误,@object-ui/react的error-message出口侧也这么判 —— 那个字符串是线上契约,改它是行为变更,而改符号只是源码改名。已加断言钉住,并在类的注释里写明。这正是批次 4 提的那条施工规约(「在记录该碰撞的文件内部同样不要做全局替换」)的正面用法:renames 全部按标识符边界替换,
toBe('ValidationError')/name: 'ValidationError'这些字面量一个没动。验证
--ledger重生成后逐条 set-diff ——removed恰为本批 12 条,added为空,其余 11 个包的条目逐字节未变(批次 7 / objectui#3161 的 14 条一个没碰)。守卫绿:11 declared dialects, 17 untriaged collisions in 11 packages。managedBy: 'system'→'system-data'retirement (objectstack#3355) #3214 落地):pnpm install --frozen-lockfile→turbo build44/44 →turbo type-check --force78/78。data-objectstack19 文件/153、plugin-chatbot17/251、plugin-list19/284、core19/153,全绿。合并前还跑过一次全量turbo test(68/73,失败见下)。plugin-chatbot/plugin-list按 objectui#3181 约定新增tsconfig.typetests.json并从type-check串接(check-type-check-coverage.mjs现报 6 个包有窄类型断言工程,原为 4)。data-objectstack不需要 —— 它的tsconfig.json本来就不排除测试文件,tsc --noEmit已经在编译这些断言;为了保住这个性质,它的 spec 名字探针改用ts.sys+ TS 自己的模块解析,而不是node:fs(否则要把@types/node塞进一个浏览器侧适配器的类型环境)。Assert< … >型别名的no-unused-vars,与已合入的 batch 5 parity 测试同形)。ViewTab改回一份忠实手抄 → 守卫按名+按文件红(TabBar.tsx:42),同时 typetests 红(spec-symbol-batch6.test.tsx(154)TS2344);ValidationError改名 → 守卫红(errors.ts:176);ViewTab留在 DEBT → 腐烂棘轮红;ListView的 ALLOW 条目 → 守卫红(同时报出ListView.tsx:456与 barrel 的index.tsx:16);src/elements/里丢一个 objectui 自己写的export interface Tool→ 守卫按设计放行(这正是路径跳过的洞),provenance 测试红并指出该文件没有 vendor banner。顺带记录(未在本 PR 修)
plugin-calendar/src/registration.test.tsx在main上就是红的(@object-ui/react的vi.mock缺subscribeDataChanges,文件级失败而非断言失败)。已把本分支全部改动git stash -u后在基线提交上复现确认。plugin-list的TabBar/TabBarSelect/ViewTab在本仓没有任何消费方(只从 barrel 导出)。没有顺手删,那是行为/API 变更,不该搭在符号燃尽 PR 上。data-objectstack的SecurityManager那一段注释还写着「Security module integration for @objectstack/spec v3.0.0」,装的是 17.0.0-rc.1;本 PR 只改了被撞名的那个类型,没有去动这段陈旧描述。草稿 PR,未自行合并。按 objectui#3160 的完成定义,objectstack#4115 的跟踪评论与本 issue 的关闭都在合入之后。